<?php
namespace Zeke\Io\Loop\Timer;
use Zeke\Contracts\Io\Loop\Timer\TimerContract;


/**
 * A scheduler implementation that can hold multiple timer instances
 *
 * This class should only be used internally, see TimerInterface instead.
 *
 * @see TimerInterface
 *
 */
final class Timers
{


    private $time;
    private $timers     = array();
    private $schedule   = array();
    private $sorted     = true;


    /**
     * 重置当前时间 微秒
     *
     * @return mixed
     * @author liu.bin 2018/8/2 18:39
     */
    public function updateTime()
    {
        return $this->time = \microtime(true);
    }


    /**
     * 获取微秒
     *
     * @return mixed
     * @author liu.bin 2018/8/2 18:40
     */
    public function getTime()
    {
        return $this->time ?: $this->updateTime();
    }


    /**
     * 注册 timer
     *
     * @param TimerContract $timer
     * @author liu.bin 2018/8/2 18:40
     */
    public function add(TimerContract $timer)
    {
        $id = \spl_object_hash($timer);
        $this->timers[$id] = $timer;
        $this->schedule[$id] = $timer->getInterval() + \microtime(true);
        $this->sorted = false;
    }


    /**
     * 是否注册当前 timer
     *
     * @param TimerContract $timer
     * @return bool
     * @author liu.bin 2018/8/2 18:40
     */
    public function contains(TimerContract $timer)
    {
        return isset($this->timers[\spl_object_hash($timer)]);
    }


    /**
     * 取消当前 timer
     *
     * @param TimerContract $timer
     * @author liu.bin 2018/8/2 18:41
     */
    public function cancel(TimerContract $timer)
    {
        $id = \spl_object_hash($timer);
        unset($this->timers[$id], $this->schedule[$id]);
    }


    /**
     * 获取首个
     *
     * @return mixed
     * @author liu.bin 2018/8/2 18:41
     */
    public function getFirst()
    {
        // ensure timers are sorted to simply accessing next (first) one
        if (!$this->sorted) {
            $this->sorted = true;
            \asort($this->schedule);
        }
        return \reset($this->schedule);
    }


    /**
     * 是否为空
     *
     * @return bool
     * @author liu.bin 2018/8/2 18:42
     */
    public function isEmpty()
    {
        return \count($this->timers) === 0;
    }



    /**
     * tick
     *
     * @author liu.bin 2018/8/2 18:42
     */
    public function tick()
    {
        // ensure timers are sorted so we can execute in order
        if (!$this->sorted) {
            $this->sorted = true;
            \asort($this->schedule);
        }

        $time = $this->updateTime();


        foreach ($this->schedule as $id => $scheduled) {
            // 调用已排序，所以循环第一个未被执行的timer
            // schedule is ordered, so loop until first timer that is not scheduled for execution now
            if ($scheduled >= $time) {
                break;
            }

            // 在执行当前定时器的时候，跳过所有移除的的timer
            // skip any timers that are removed while we process the current schedule
            if (!isset($this->schedule[$id]) || $this->schedule[$id] !== $scheduled) {
                continue;
            }
            $timer = $this->timers[$id];
            \call_user_func($timer->getCallback(), $timer);

            //如果是个没有取消的周期性定时器，则需要重置
            // re-schedule if this is a periodic timer and it has not been cancelled explicitly already
            if ($timer->isPeriodic() && isset($this->timers[$id])) {
                $this->schedule[$id] = $timer->getInterval() + $time;
                $this->sorted = false;
            } else {
                unset($this->timers[$id], $this->schedule[$id]);
            }
        }
    }


}